use std::{ops::Mul};

use super::vec4::Vec4;
use std::fmt;

/// Matrices are stored by columm array

#[derive(Copy, Clone)]
#[repr(C)]
pub struct Mat4 {
    pub m: [[f32; 4]; 4]
}

macro_rules! mulv {
    ($m1: expr => row $i:expr; $m2: expr => col $j: expr) => {
        $m1[0][$i] * $m2[$j][0] + 
        $m1[1][$i] * $m2[$j][1] + 
        $m1[2][$i] * $m2[$j][2] + 
        $m1[3][$i] * $m2[$j][3]
    };
    ($m1: expr => row $i: expr; $v: expr) => {
        $m1[0][$i] * $v.0 +
        $m1[1][$i] * $v.1 +
        $m1[2][$i] * $v.2 +
        $m1[3][$i] * $v.3
    };
}

impl Mat4 {
    pub fn from_col(col0: Vec4, col1: Vec4, col2: Vec4, col3: Vec4) -> Self {
        Self {
            m: [
                col0.to_array(),
                col1.to_array(),
                col2.to_array(),
                col3.to_array()
            ]
        }
    }

    pub fn identity() -> Self {
        Self {
            m: [
                [1.0, 0.0, 0.0, 0.0],
                [0.0, 1.0, 0.0, 0.0],
                [0.0, 0.0, 1.0, 0.0],
                [0.0, 0.0, 0.0, 1.0]
            ]
        }
    }

    pub fn new_scale(x: f32, y: f32, z: f32) -> Self {
        Self {
            m: [
                [x,   0.0, 0.0, 0.0],
                [0.0, y,   0.0, 0.0],
                [0.0, 0.0, z,   0.0],
                [0.0, 0.0, 0.0, 1.0]
            ]
        }
    }

    pub fn translation(x: f32, y: f32, z: f32) -> Self {
        Self {
            m: [
                [1.0, 0.0, 0.0, 0.0],
                [0.0, 1.0, 0.0, 0.0],
                [0.0, 0.0, 1.0, 0.0],
                [  x,   y,   z, 1.0]
            ]
        }
    }

    pub fn translation_vec(translate: Vec3) -> Self {
        Self::translation(translate.0, translate.1, translate.2)
    }

    pub fn rotation_x(rad: f32) -> Self {
        let sin = rad.sin();
        let cos = rad.cos();
        Self {
            m: [
                [1.0,  0.0, 0.0, 0.0],
                [0.0,  cos, sin, 0.0],
                [0.0, -sin, cos, 0.0],
                [0.0,  0.0, 0.0, 1.0],
            ]
        }
    }

    pub fn rotation_y(rad: f32) -> Self {
        let sin = rad.sin();
        let cos = rad.cos();
        Self {
            m: [
                [cos, 0.0, -sin, 0.0],
                [0.0, 1.0,  0.0, 0.0],
                [sin, 0.0,  cos, 0.0],
                [0.0, 0.0,  0.0, 1.0],
            ]
        }
    }

    pub fn rotation_z(rad: f32) -> Self {
        let sin = rad.sin();
        let cos = rad.cos();
        Self {
            m: [
                [ cos, sin, 0.0, 0.0],
                [-sin, cos, 0.0, 0.0],
                [ 0.0, 0.0, 1.0, 0.0],
                [ 0.0, 0.0, 0.0, 1.0],
            ]
        }
    }

    pub fn orthogonal(left: f32, right: f32, bottom: f32, top: f32, far: f32, near: f32) -> Self {
        let dx = right - left;
        let dy = top - bottom;
        let dz = far - near;

        Self {
            m: [
                [2.0 / dx, 0.0, 0.0, 0.0],
                [0.0, 2.0 / dy, 0.0, 0.0],
                [0.0, 0.0, -2.0 / dz, 0.0],
                [-(right + left) / dx, -(top + bottom) / dy, -(far + near) / dz, 1.0]
            ]
        }
    }

    pub fn ortho(w: f32, h: f32, f: f32, n: f32) -> Self {
        let dz = f - n;
        Self {
            m: [
                [2.0 / w, 0.0, 0.0, 0.0],
                [0.0, 2.0 / h, 0.0, 0.0],
                [0.0, 0.0, 1.0 / dz, 0.0],
                [0.0, 0.0, -n / dz, 1.0]
            ]
        }
    }

    pub fn look_at(eye: Vec3, center: Vec3, up: Vec3) -> Self {
        let z_axis = (center - eye).normalized();
        let x_axis = Vec3::cross(up.normalized(), z_axis).normalized();
        let y_axis = Vec3::cross(z_axis, x_axis);

        // let mut rotate = Mat4::identity();
        // rotate.m[0][0] = x_axis.0;
        // rotate.m[0][1] = x_axis.1;
        // rotate.m[0][2] = x_axis.2;

        // rotate.m[1][0] = y_axis.0;
        // rotate.m[1][1] = y_axis.1;
        // rotate.m[1][2] = y_axis.2;

        // rotate.m[2][0] = z_axis.0;
        // rotate.m[2][1] = z_axis.1;
        // rotate.m[2][2] = z_axis.2;

        // let m = rotate * Self::translation_vec(eye);
        let m = [
            [x_axis.0, x_axis.1, x_axis.2, 0.0],
            [y_axis.0, y_axis.1, y_axis.2, 0.0],
            [z_axis.0, z_axis.1, z_axis.2, 0.0],
            [eye.0, eye.1, eye.2, 0.0]
        ];
        Self {
            m
        }
    }

    pub fn to_array_transply(&self) -> [f32; 16] {
        [
            self.m[0][0], self.m[1][0], self.m[2][0], self.m[3][0],
            self.m[0][1], self.m[1][1], self.m[2][1], self.m[3][1],
            self.m[0][2], self.m[1][2], self.m[2][2], self.m[3][2],
            self.m[0][3], self.m[1][3], self.m[2][3], self.m[3][3],
        ]
    }

    pub fn to_array(&self) -> [f32; 16] {
        [                                                                     
            self.m[0][0], self.m[0][1], self.m[0][2], self.m[0][3],
            self.m[1][0], self.m[1][1], self.m[1][2], self.m[1][3],
            self.m[2][0], self.m[2][1], self.m[2][2], self.m[2][3],
            self.m[3][0], self.m[3][1], self.m[3][2], self.m[3][3],
        ]
    }

    pub fn as_array(&self) -> &[f32; 16] {
        unsafe {
            std::mem::transmute(&self.m)
        }
    }

    pub fn as_ptr(&self) -> *const f32 {
        &self.m[0][0]
    }

    pub fn load_identity(&mut self) {
        for i in 0..4 {
            for j in 0..4 {
                if i == j {
                    self.m[i][j] = 1.0;
                } else {
                    self.m[i][j] = 0.0;
                }
            }
        }
    }

    pub fn translate(&mut self, tx: f32, ty: f32, tz: f32) {
        self.m[3][0] += self.m[0][0] * tx + self.m[1][0] * ty + self.m[2][0] * tz;
        self.m[3][1] += self.m[0][1] * tx + self.m[1][1] * ty + self.m[2][1] * tz;
        self.m[3][2] += self.m[0][2] * tx + self.m[1][2] * ty + self.m[2][2] * tz;
        self.m[3][3] += self.m[0][3] * tx + self.m[1][3] * ty + self.m[2][3] * tz;
    }

    pub fn scale(&mut self, sx: f32, sy: f32, sz: f32) {
        self.m[0][0] *= sx;
        self.m[0][1] *= sx;
        self.m[0][2] *= sx;

        self.m[1][0] *= sy;
        self.m[1][1] *= sy;
        self.m[1][2] *= sy;

        self.m[2][0] *= sz;
        self.m[2][1] *= sz;
        self.m[2][2] *= sz;
    }

    pub fn col(&self, idx: usize) -> Vec4 {
        Vec4(self.m[idx][0], self.m[idx][1], self.m[idx][2], self.m[idx][3])
    }

    pub fn row(&self, idx: usize) -> Vec4 {
        Vec4(self.m[0][idx], self.m[1][idx], self.m[2][idx], self.m[3][idx])
    }
}
use super::vec3::Vec3;

impl Mul<f32> for Mat4 {
    type Output = Self;
    fn mul(mut self, rhs: f32) -> Self {
        for i in unsafe { std::mem::transmute::<_, &mut [f32; 16]>(&mut self.m)} {
            *i *= rhs;
        }
        Self {
            m: self.m
        }
    }
}

impl Mul for Mat4 {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self {
        Self::from_col(
            Vec4 (
                mulv!(self.m => row 0; rhs.m => col 0),
                mulv!(self.m => row 1; rhs.m => col 0),
                mulv!(self.m => row 2; rhs.m => col 0),
                mulv!(self.m => row 3; rhs.m => col 0),
            ),
            Vec4(
                mulv!(self.m => row 0; rhs.m => col 1),
                mulv!(self.m => row 1; rhs.m => col 1),
                mulv!(self.m => row 2; rhs.m => col 1),
                mulv!(self.m => row 3; rhs.m => col 1),
            ),
            Vec4(
                mulv!(self.m => row 0; rhs.m => col 2),
                mulv!(self.m => row 1; rhs.m => col 2),
                mulv!(self.m => row 2; rhs.m => col 2),
                mulv!(self.m => row 3; rhs.m => col 2),
            ),
            Vec4(
                mulv!(self.m => row 0; rhs.m => col 3),
                mulv!(self.m => row 1; rhs.m => col 3),
                mulv!(self.m => row 2; rhs.m => col 3),
                mulv!(self.m => row 3; rhs.m => col 3),
            )
        )
    }
}

impl Mul<Vec4> for Mat4 {
    type Output = Vec4;

    fn mul(self, rhs: Vec4) -> Self::Output {
        Vec4(
            mulv!(self.m => row 0; rhs), 
            mulv!(self.m => row 1; rhs),
            mulv!(self.m => row 2; rhs), 
            mulv!(self.m => row 3; rhs)
        )
    }
}

impl fmt::Display for Mat4 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}\n{}\n{}\n{}", self.row(0), self.row(1), self.row(2), self.row(3))
    }
}

impl Default for Mat4 {
    fn default() -> Self {
        Self::identity()
    }
}